home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Internet / News / Alexandra.0.82 / Source / ArticleViewControl.m < prev    next >
Encoding:
Text File  |  1996-01-30  |  10.5 KB  |  489 lines

  1. #import "Alexandra.h"
  2. #import "ArticleViewControl.h"
  3. #import <misckit/MiscAppDefaults.h>
  4. #import "response_codes.h"
  5. #import "FaceView.h"
  6. #import <misckit/MiscClockView.h>
  7. #import "parse-header.h"
  8. #import "rfc822realname.h"
  9. #include <ctype.h>
  10.  
  11. #define QUOTESTRING ">"
  12. #define ISEMPTY(string) ((string==NULL)||(*(string)=='\0'))
  13. #define FORCEBREAK "\n\t -•1234567890>#}:"
  14.  
  15. @implementation ArticleViewControl
  16.  
  17. - init
  18. {
  19.    [super init];
  20.    noArticle=TRUE;
  21.     rot13=FALSE;
  22.     [ERROR_MANAGER addObserver:self
  23.                          selector:@selector(updateText)
  24.                         forError:ENOTEPrefsChanged];
  25.     [self updateText];
  26.     
  27.    return self;
  28. }
  29.  
  30. - awakeFromNib
  31. {
  32.    [theText setFontPanelEnabled:FALSE];
  33.    [clockView setMilitaryTime:[NXApp defaultBoolValue:"24HourClock"]];
  34.     [clockView setHide:YES];
  35.     
  36.     return self;
  37. }
  38.  
  39. - free
  40. {
  41.    if(articleHeader)
  42.       free(articleHeader);
  43.     if(articleBody)
  44.         free(articleBody);
  45.     [ERROR_MANAGER removeObserver:self forError:ENOTEPrefsChanged];
  46.  
  47.    return [super free];
  48. }
  49.  
  50. - updateText
  51. {
  52.     [self displayArticleScrollUp:NO];
  53.     
  54.    return self;
  55. }
  56.  
  57. - (int)loadArticle:(Article *)theArticle fromGroup:(const char *)theGroup
  58. {
  59.    int statusCode;
  60.     time_t t;
  61.  
  62.     rot13=FALSE;
  63.    // get article header + body
  64.     if(articleHeader)
  65.       free(articleHeader);    
  66.    statusCode=[nntpServer loadArticleHeader:theArticle toString:&articleHeader];
  67.    if(statusCode!=OK_HEAD){
  68.       return statusCode;
  69.    }
  70.  
  71.     subject=[theArticle header]->fieldBody[SUBJECT];
  72.     from=[theArticle header]->fieldBody[FROM];
  73.     organization=[theArticle header]->fieldBody[ORGANIZATION];
  74.  
  75.     
  76.     if(articleBody)
  77.       free(articleBody);    
  78.    statusCode=[nntpServer loadArticleBody:theArticle toString:&articleBody];
  79.    if(statusCode!=OK_BODY){
  80.       return statusCode;
  81.    }
  82.     
  83.     noArticle=FALSE;
  84.     [self displayArticleScrollUp:YES];
  85.  
  86.    t=[theArticle time];
  87.    if(![imageView showFaceForName:[theArticle header]->fieldBody[FROM]]){
  88.         const char *r=[theArticle header]->fieldBody[REPLY_TO];
  89.         if(r && *r)
  90.             [imageView showFaceForName:r];
  91.     }
  92.    [[[clockView setHide:FALSE] setTime:localtime(&t)] display];
  93.     
  94.     return OK_BODY;
  95. }
  96.  
  97.  
  98. - displayArticleScrollUp:(BOOL)scroll
  99. {
  100.     Font        *textF,*sigF;
  101.     const char *signature,*eob,*p,*thisLine;
  102.     int            len,rows,alnum,others;
  103.     NXSize visibleSize;
  104.     NXRect theUpperRect;
  105.     char *tmpArticleBody=articleBody;
  106.     
  107.     signatureDetection=[NXApp defaultBoolValue:DEFAULT_SIG_DETECTION];
  108.     rewrapping=[NXApp defaultBoolValue:DEFAULT_REWRAP_ARTICLE_TEXT];
  109.     headerMode=[NXApp defaultIntValue:DEFAULT_HEADER_MODE];
  110.  
  111.     if(noArticle)
  112.         return self;
  113.  
  114.     // rot13 article body?
  115.     if(rot13){
  116.         int i,j;
  117.         j=strlen(articleBody);
  118.         tmpArticleBody=malloc((j+2)*sizeof(char));
  119.         for(i=0;i<j;i++) {
  120.         if(islower(articleBody[i]))
  121.                 tmpArticleBody[i] = (((articleBody[i]-'a')+13)%26)+'a';
  122.         else if(isupper(articleBody[i]))
  123.                   tmpArticleBody[i] = (((articleBody[i]-'A')+13)%26)+'A';
  124.           else
  125.                 tmpArticleBody[i]=articleBody[i];
  126.       }
  127.     }
  128.         
  129.     [[theText window] disableDisplay];
  130.     [theText setAutodisplay:NO];
  131.     [theText setText:""];
  132.  
  133.     textF=[NXApp defaultFont:DEFAULT_ARTICLE_FONT];
  134.     
  135.     sigF=[Font userFixedPitchFontOfSize:0 matrix:NX_FLIPPEDMATRIX];
  136.     [theText setFont:textF];
  137.         
  138.    //load text in text instance
  139.     if(headerMode==NEWSPAPER_HEADER){
  140.         [self writeNewspaperHeader];    
  141.     }
  142.     else if(headerMode==FULL_HEADER){
  143.         [theText setText:articleHeader];
  144.     }
  145.     else if(headerMode==SMALL_HEADER){
  146.         [self writeSelectedHeader];      
  147.     }
  148.     len=[theText textLength];
  149.     [theText setSel:len :len];
  150.     [theText replaceSel:"\n"];
  151.  
  152.     signature=NULL;
  153.     len=strlen(tmpArticleBody);
  154.     if(len>4){
  155.         eob=tmpArticleBody+len-1;
  156.         signature=NULL;
  157.         //find signature
  158.         if(signatureDetection){
  159.             rows=0;
  160.             for(p=tmpArticleBody+len-4;p>tmpArticleBody && rows<16;p--)
  161.                 if(p[0]=='\n'){
  162.                     if(p[1]=='-' && p[2]=='-' && p[strspn(p+1,"\t -")+1]=='\n'){
  163.                         eob=p;
  164.                         signature=p+3;
  165.                         if(*signature=='-')
  166.                             signature++;
  167.                         break;
  168.                     }
  169.                     else
  170.                         rows++;
  171.                 }
  172.         }
  173.             
  174.         thisLine=tmpArticleBody;
  175.         do{
  176.             alnum=others=1;
  177.             for(p=thisLine;p<eob && p[0]!='\n';p++)
  178.                 if(NXIsAlNum(p[0]) || p[0]==' ')
  179.                     alnum++;
  180.                 else
  181.                     others++;
  182.                 
  183.             if(rewrapping && p!=eob && alnum>40 && alnum/others>3 && !strchr(FORCEBREAK,p[1]))
  184.                 {
  185.                 [theText appendString:thisLine length:p-thisLine withFont:textF];
  186.                 if(p[-1]!=' ')
  187.                     [theText appendString:" " withFont:textF];
  188.                 }
  189.             else
  190.                 [theText appendString:thisLine length:p-thisLine+1 withFont:textF];
  191.                 
  192.             thisLine=p+1;
  193.         }
  194.         while(p<eob);
  195.  
  196.         if(signatureDetection && signature)
  197.             {
  198.             [theText appendString:"\n" withFont:sigF];
  199.             [theText appendString:signature withFont:sigF];
  200.             }
  201.     }
  202.     else
  203.         [theText appendString:tmpArticleBody length:len withFont:textF];
  204.     
  205.     if(scroll){
  206.        [[[theText superview] superview] getContentSize:&visibleSize];
  207.        NXSetRect(&theUpperRect,0.0,0.0,visibleSize.width,visibleSize.height);
  208.        [theText scrollRectToVisible:&theUpperRect];
  209.     }
  210.     
  211.    [theText setAutodisplay:YES];
  212.    [[theText window] reenableDisplay];
  213.    [[theText window] display];
  214.  
  215.     if(rot13)
  216.         free(tmpArticleBody);
  217.         
  218.    return self;
  219. }
  220.  
  221. - clear
  222. {
  223.    [theText setAutodisplay:NO];
  224.    [theText setText:""];
  225.    [[theText setAutodisplay:YES] display];
  226.     
  227.    noArticle=TRUE;
  228.    [imageView showFaceForName:NULL];
  229.    [[clockView setHide:TRUE] display];
  230.     if(articleBody)
  231.         free(articleBody);
  232.     if(articleHeader)
  233.         free(articleHeader);
  234.     articleBody=NULL;
  235.     articleHeader=NULL;
  236.     
  237.    return self;
  238. }
  239.  
  240. - saveAs:sender
  241. {
  242.    id panel;
  243.    int fd;
  244.    NXStream *theStream;
  245.    static char *dir=NULL;
  246.  
  247.    if(dir==NULL)
  248.       dir=NXCopyStringBuffer([NXApp defaultValue:DEFAULT_SAVE_PATH]);
  249.    if([theText textLength]==0 || noArticle)
  250.       return self;
  251.    panel=[SavePanel new];
  252.    if([panel runModalForDirectory:dir file:""]!=NX_CANCELTAG){
  253.       free(dir);
  254.       dir=NXCopyStringBuffer([panel filename]);
  255.    }
  256.    else
  257.       return nil;
  258.    
  259.    //Save
  260.    fd=open([panel filename],O_WRONLY|O_CREAT|O_TRUNC,0666);
  261.    if(fd<0){
  262.       NXRunAlertPanel("ALEXANDRA","Can't save file: %s",NULL,NULL,NULL,strerror(errno));
  263.       return self;
  264.    }
  265.    theStream=NXOpenFile(fd,NX_WRITEONLY);
  266.     NXWrite(theStream,articleHeader,strlen(articleHeader));
  267.     NXWrite(theStream,"\n",1);
  268.     NXWrite(theStream,articleBody,strlen(articleBody));
  269.    NXClose(theStream);
  270.    close(fd);
  271.    return self;
  272. }
  273.  
  274. - printText:sender
  275. {
  276.    if([theText textLength]!=0){
  277.         [[NXApp printInfo] setHorizPagination:NX_FITPAGINATION];
  278.       [theText printPSCode:self];
  279.     }
  280.    return self;
  281. }
  282.  
  283. - (const char *)articleBody
  284. {
  285.     if(noArticle)
  286.         return NULL;
  287.     return articleBody;
  288. }
  289.  
  290. - (const char *)articleHeader
  291. {
  292.    if(noArticle)
  293.         return NULL;
  294.     return articleHeader;
  295. }
  296.  
  297. - writeBody:(NXStream *)aStream
  298. {
  299.     if(articleBody)
  300.         NXWrite(aStream,articleBody,strlen(articleBody));
  301.  
  302.    return self;
  303. }
  304.  
  305. - writeQuotedText:(NXStream *)aStream
  306. {
  307.    int i;
  308.    int bodyLen;
  309.    BOOL wasNewline;
  310.    const char *quotestring;
  311.  
  312.     if(!articleBody)
  313.         return self;
  314.         
  315.    quotestring=[NXApp defaultValue:DEFAULT_QUOTING_PREFIX];
  316.    if(!quotestring){
  317.       [NXApp setDefault:DEFAULT_QUOTING_PREFIX to:QUOTESTRING];
  318.       quotestring=[NXApp defaultValue:DEFAULT_QUOTING_PREFIX];
  319.    }
  320.  
  321.     bodyLen=strlen(articleBody);
  322.    wasNewline=TRUE;
  323.    for(i=0;i<bodyLen;i++){
  324.       if(wasNewline)
  325.          NXPrintf(aStream,"%s",quotestring);
  326.       NXPutc(aStream,articleBody[i]);
  327.       if(articleBody[i]=='\n')
  328.          wasNewline=TRUE;
  329.       else
  330.          wasNewline=FALSE;
  331.    }   
  332.    
  333.    return self;
  334. }
  335.  
  336. - writeSelectedHeader
  337. {
  338.    char **selectedHeaders;
  339.    char *field_names,*f_buffer,**pattern;
  340.    const char *default_headers;
  341.    int count=0;
  342.    int num_h=0;
  343.    int i;
  344.    BOOL start=TRUE;
  345.     NXStream *aStream=NXOpenMemory(NULL,0,NX_READWRITE);
  346.  
  347.    default_headers=[NXApp defaultValue:DEFAULT_HEADER_FILTER];
  348.    if(*default_headers=='\0'){
  349.       return self;
  350.    }
  351.  
  352.    field_names=NXCopyStringBuffer(default_headers);
  353.    for(f_buffer=field_names;*f_buffer!='\0';f_buffer++)
  354.       if(*f_buffer==':')
  355.          num_h++;
  356.    selectedHeaders=calloc(num_h,sizeof(char**));
  357.    pattern=calloc(num_h,sizeof(char**));
  358.  
  359.    f_buffer=field_names; i=0;
  360.    for(;*f_buffer!='\0';f_buffer++){
  361.       if(start){
  362.          pattern[i]=f_buffer;
  363.          start=FALSE;
  364.          i++;
  365.       }
  366.       else if(*f_buffer==':'){
  367.          start=TRUE;
  368.          *f_buffer='\0';
  369.       }
  370.    }
  371.    
  372.    parse_header(articleHeader,strlen(articleHeader),selectedHeaders,pattern,num_h,NO);
  373.  
  374.    i=0;   
  375.    for(count=0;count<num_h;count++)
  376.       if(selectedHeaders[count]!=NULL){
  377.          i++;
  378.          NXPrintf(aStream,"%s: %s\n",pattern[count],selectedHeaders[count]);
  379.          free(selectedHeaders[count]);
  380.       }
  381.    NXPrintf(aStream,"\n");
  382.  
  383.    free(field_names);
  384.    free(selectedHeaders);
  385.    free(pattern);
  386.     NXSeek(aStream,0,NX_FROMSTART);
  387.     [theText readText:aStream];
  388.     NXCloseMemory(aStream,NX_FREEBUFFER);
  389.  
  390.    return self;
  391. }
  392.  
  393. - writeNewspaperHeader
  394. {
  395.     Font        *titleF,*authorF;
  396.     
  397.     NXRect visibleRect;
  398.     int titleFsize=24;
  399.     
  400.     [theText getVisibleRect:&visibleRect];
  401.     
  402.     authorF=[Font boldSystemFontOfSize:14 matrix:NX_FLIPPEDMATRIX];
  403.  
  404.     if(!ISEMPTY(subject)){
  405.         while(([titleF=[Font boldSystemFontOfSize:titleFsize matrix:NX_FLIPPEDMATRIX]
  406.                     getWidthOf:subject]>NX_WIDTH(&visibleRect)-10) && titleFsize>12)
  407.             titleFsize-=2;
  408.  
  409.         [theText appendString:subject withFont:titleF];
  410.         [theText appendString:"\n" withFont:titleF];
  411.     }
  412.     if(!ISEMPTY(from)){
  413.         int nameFrom,to;
  414.         
  415.         rfc822_realname(from,&nameFrom,&to);
  416.         [theText appendString:"by " withFont:authorF];
  417.         [theText appendString:from+nameFrom length:to-nameFrom+1 withFont:authorF];
  418.         if(!ISEMPTY(organization)){
  419.             [theText appendString:", " withFont:authorF];
  420.             [theText appendString:organization withFont:authorF];
  421.         }
  422.         [theText appendString:"\n" withFont:authorF];
  423.     }
  424.  
  425.     return self;
  426. }
  427.  
  428. -(id)theText
  429. {
  430.    return theText;
  431. }
  432.  
  433. - setModeTo:(int)mode
  434. {
  435.    [NXApp setDefault:DEFAULT_HEADER_MODE toInt:mode];
  436.    if(!noArticle)
  437.         [self displayArticleScrollUp:NO];
  438.  
  439.    return self;
  440. }
  441.       
  442. - showHeader:sender
  443. {
  444.    [self setModeTo:FULL_HEADER];
  445.    return self;
  446. }
  447.  
  448. - hideHeader:sender
  449. {
  450.    [self setModeTo:NO_HEADER];
  451.    return self;
  452. }
  453.  
  454. - smallHeader:sender
  455. {
  456.    [self setModeTo:SMALL_HEADER];
  457.    return self;
  458. }
  459.  
  460. - newspaperHeader:sender
  461. {
  462.     [self setModeTo:NEWSPAPER_HEADER];
  463.     return self;
  464. }
  465.  
  466. - (BOOL)headermodeCellEnabled:menuCell
  467. {
  468.    if((headerMode!=FULL_HEADER)&&([menuCell action]==@selector(showHeader:)))
  469.       return TRUE;
  470.    else if((headerMode!=NO_HEADER)&&([menuCell action]==@selector(hideHeader:)))
  471.       return TRUE;
  472.    else if((headerMode!=SMALL_HEADER)&&([menuCell action]==@selector(smallHeader:)))
  473.       return TRUE;
  474.     else if((headerMode!=NEWSPAPER_HEADER)&&([menuCell action]==@selector(newspaperHeader:)))
  475.         return TRUE;
  476.    return FALSE;
  477. }
  478.  
  479. - rot13:sender
  480. {
  481.     rot13=!rot13;
  482.     
  483.     [self displayArticleScrollUp:NO];
  484.  
  485.     return self;
  486. }
  487.  
  488. @end
  489.